package com.franklin.ideaplugin.easytesting.ui.execute;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.franklin.ideaplugin.api.beans.EnumClass;
import com.franklin.ideaplugin.api.beans.HeaderMapping;
import com.franklin.ideaplugin.api.ui.IBaseToolBarComponent;
import com.franklin.ideaplugin.api.ui.editor.CustomEditor;
import com.franklin.ideaplugin.api.utils.ProjectUtils;
import com.franklin.ideaplugin.api.utils.PsiUtil;
import com.franklin.ideaplugin.easytesting.cache.IEasyTestingCache;
import com.franklin.ideaplugin.easytesting.cache.impl.PersistenceEasyTestingCache;
import com.franklin.ideaplugin.easytesting.common.entity.ETRsp;
import com.franklin.ideaplugin.easytesting.configuration.UIConfigPersistentState;
import com.franklin.ideaplugin.easytesting.constant.EasyTestingHeaders;
import com.franklin.ideaplugin.easytesting.entity.ExecuteResult;
import com.franklin.ideaplugin.easytesting.entity.PressureTestConfig;
import com.franklin.ideaplugin.easytesting.rpc.RpcClient;
import com.franklin.ideaplugin.easytesting.rpc.IExecuteResultHandler;
import com.franklin.ideaplugin.easytesting.rpc.RegistryCenter;
import com.franklin.ideaplugin.easytesting.rpc.entity.MethodInvokeData;
import com.franklin.ideaplugin.easytesting.rpc.entity.MethodParameter;
import com.franklin.ideaplugin.easytesting.rpc.entity.RegistryData;
import com.franklin.ideaplugin.easytesting.thread.EasyTestingThreadPool;
import com.franklin.ideaplugin.easytesting.thread.pressuretest.PressureTestTaskStarter;
import com.franklin.ideaplugin.easytesting.ui.HttpHeaderCellRenderer;
import com.franklin.ideaplugin.easytesting.ui.HttpHeaderTableModel;
import com.franklin.ideaplugin.easytesting.ui.Labels;
import com.franklin.ideaplugin.easytesting.ui.Sizes;
import com.franklin.ideaplugin.easytesting.ui.pressuretest.PressureTestConfigDialog;
import com.franklin.ideaplugin.easytesting.utils.PsiUtils;
import com.franklin.ideaplugin.api.utils.SwingUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileChooser.*;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.VirtualFileWrapper;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBScrollPane;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.io.File;
import java.util.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * @author Ye Junhui
 * @since 2023/5/12
 */
public class ExecuteMethodFormNew implements IBaseToolBarComponent {
    private JPanel mainPanel;
    private JButton executeBtn;
    private JButton downloadBtn;
    private JButton rollbackExecuteBtn;
    private JButton threadPoolExecuteBtn;
    private JPanel headerPanel;
    private JPanel methodParamPanel;
    private JTable headerTable;
    private JButton headerAddBtn;
    private JButton headerRemoveBtn;
    private JPanel methodParamParentPanel;
    private JScrollPane methodParamScroll;
    private JScrollPane headerScrollPane;
    private JPanel toolPanel;
    private JSplitPane headerSplitPane;

    private PsiMethod psiMethod;

    private Runnable cancelWorker = () -> {
    };
    private volatile boolean isDownload = false;
    private volatile String downloadPath;
    private volatile boolean isTxRollbackCover = false;

    /**
     * 缓存填充值
     */
    private Map<String, ParameterTextComponent> paramMap = Maps.newLinkedHashMap();
    private Map<String, PsiParameter> psiParamMap = Maps.newLinkedHashMap();
    private Map<String, MethodParameter> executeParamMap = Maps.newLinkedHashMap();

    private CustomEditor headerEditor;

    private ComboBox<String> enumInstanceComboBox;

    private List<PsiParameter> psiParameterList;

    private List<String> instanceMethodEnumList = Lists.newArrayList();

    private final Map<String, Object> historyParamMap;

    private IExecuteResultHandler executeResultHandler;

    private String scriptPath;
    private ETRsp<?> lastRsp;
//    private final PsiFile containingFile;

    private BaseExecuteForm baseExecuteForm;

    public ExecuteMethodFormNew(
            PsiMethod psiMethod,
            Map<String, Object> historyParamMap,
            IExecuteResultHandler<ExecuteResult> executeResultHandler,
            JButton executeBtn,
            JButton downloadBtn,
            JButton rollbackExecuteBtn,
            JButton threadPoolExecuteBtn,
            String scriptPath,
            ETRsp<?> lastRsp) {
        this.psiMethod = psiMethod;
//        this.containingFile = psiMethod.getContainingClass().getContainingFile();
        this.historyParamMap = historyParamMap;
        this.paramMap = Maps.newLinkedHashMap();
        this.psiParamMap = Maps.newLinkedHashMap();
        this.psiParameterList = PsiUtils.getPsiMethodParameters(psiMethod);
        this.instanceMethodEnumList = getInstanceMethodEnumList();
        this.executeResultHandler = executeResultHandler;
        this.executeBtn = executeBtn;
        this.downloadBtn = downloadBtn;
        this.rollbackExecuteBtn = rollbackExecuteBtn;
        this.threadPoolExecuteBtn = threadPoolExecuteBtn;
        this.scriptPath = scriptPath;
        this.lastRsp = lastRsp;

        this.baseExecuteForm = new BaseExecuteForm(
                psiMethod,
                methodParamPanel,
                psiParameterList,
                instanceMethodEnumList,
                historyParamMap,
                paramMap,
                psiParamMap,
                executeParamMap
        );
        this.init();
    }


    /**
     * 初始化
     */
    private void init() {
        initHeaderPanel();
        initParamPanel();
        initExecuteBtn();
        initDownloadBtn();
        initTxRollbackCoverBtn();
        initPressureTestExecuteBtn();
    }

    private void initHeaderPanel() {
        UIConfigPersistentState uiConfigPersistentState = UIConfigPersistentState.getInstance();
        this.headerSplitPane.setDividerLocation(uiConfigPersistentState.headerHeight);

        IEasyTestingCache easyTestingCache = IEasyTestingCache.getInstance(ProjectUtils.getCurrProject());
        //是否全选
        boolean notSelectAll = easyTestingCache.getHeaderStatusMap().values().stream()
                .anyMatch(status -> status == Boolean.FALSE);

        HttpHeaderCellRenderer httpHeaderCellRenderer = new HttpHeaderCellRenderer(!notSelectAll, this.headerTable);
        HttpHeaderTableModel httpHeaderTableModel = new HttpHeaderTableModel(httpHeaderCellRenderer.getCheckBox(), this.headerTable);


        this.headerTable.setModel(httpHeaderTableModel);
        this.headerTable.getTableHeader().setDefaultRenderer(httpHeaderCellRenderer);

        //历史记录
        Map<String, String> headerMap = easyTestingCache.getHeaderMap();
        Map<String, Boolean> headerStatusMap = easyTestingCache.getHeaderStatusMap();
        if (CollectionUtil.isNotEmpty(headerMap)) {
            headerMap.forEach((k, v) -> {
                httpHeaderTableModel.addRow(
                        new HeaderMapping(headerStatusMap.getOrDefault(k, true), k, v)
                );
            });
        }

        //添加
        this.headerAddBtn = new JButton();
        this.headerAddBtn.setIcon(AllIcons.General.Add);
        this.headerAddBtn.setText("Add http header");
        this.headerAddBtn.addActionListener(e -> {
            httpHeaderTableModel.addRow(new HeaderMapping());
        });

        //删除
        this.headerRemoveBtn = new JButton();
        this.headerRemoveBtn.setIcon(AllIcons.General.Remove);
        this.headerRemoveBtn.setText("Remove http header");
        this.headerRemoveBtn.addActionListener(e -> {
            int[] selectedRows = this.headerTable.getSelectedRows();
            if (ArrayUtil.isNotEmpty(selectedRows)) {
                if (SwingUtils.showConfirmDialog("Easy Testing", "Confirm to remove selected headers?")) {
                    for (int i = 0; i < selectedRows.length; i++) {
                        httpHeaderTableModel.removeRow(selectedRows[i]);
                    }
                }
            }
        });

        //工具栏
        DefaultActionGroup actionGroup = new DefaultActionGroup();
        this.initToolBar(actionGroup);
        this.addToolButton(actionGroup, this.headerAddBtn);
        this.addToolButton(actionGroup, this.headerRemoveBtn);
    }

    private void initParamPanel() {
        int size = psiParameterList.size();
        if (CollectionUtil.isNotEmpty(instanceMethodEnumList)) {
            size++;
        }
        if (size > 0) {
            methodParamPanel.setLayout(SwingUtils.createLayoutManager(size, 2));
        }
        initEnumInstanceMethod();
//        initMethodParamPanel();
        this.baseExecuteForm.initMethodParamPanel();
    }

    /**
     * 枚举实例初始化
     */
    private void initEnumInstanceMethod() {
        //枚举选择
        if (CollectionUtil.isNotEmpty(instanceMethodEnumList)) {
            enumInstanceComboBox = new ComboBox<>(instanceMethodEnumList.toArray(new String[0]));
            enumInstanceComboBox.setSelectedIndex(0);
            String name = psiMethod.getContainingClass().getName();
            JBLabel jbLabel = new JBLabel(String.format(Labels.CHOOSE_ENUM_LABEL, name));
            jbLabel.setBorder(BorderFactory.createLoweredBevelBorder());
            this.addCell(methodParamPanel, jbLabel, 0, 0, Sizes.ExecuteMethodFormSize.ENUM_LABEL_SIZE);
            this.addCell(methodParamPanel, enumInstanceComboBox, 0, 1, Sizes.ExecuteMethodFormSize.CHILD_SIZE);
        }
    }

    /**
     * 获取带实例方法的枚举列表
     *
     * @return
     */
    private List<String> getInstanceMethodEnumList() {
        try {
            if (PsiUtil.isStaticMethod(psiMethod)) {
                return Collections.emptyList();
            }
            PsiClass psiClass = psiMethod.getContainingClass();
            if (Objects.isNull(psiClass) || !psiClass.isEnum()) {
                return Collections.emptyList();
            }
            EnumClass enumClass = PsiUtil.getDefaultDataOfEnum(psiClass);
            if (Objects.isNull(enumClass)) {
                return Collections.emptyList();
            }
            List<String> enumList = enumClass.getEnumList();
            if (CollectionUtil.isNotEmpty(enumList)) {
                return enumList;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return Collections.emptyList();
    }

    /**
     * 初始化事务回滚执行按钮
     */
    private void initTxRollbackCoverBtn() {
        SwingUtils.clearActionListener(this.rollbackExecuteBtn);
        this.rollbackExecuteBtn.addActionListener(e -> {
            try {
                this.isTxRollbackCover = true;
                this.executeBtn.doClick();
            } finally {
                this.isDownload = false;
                this.isTxRollbackCover = false;
            }
        });
    }

    /**
     * 初始化压力测试执行按钮
     */
    private void initPressureTestExecuteBtn() {
        SwingUtils.clearActionListener(this.threadPoolExecuteBtn);
        this.threadPoolExecuteBtn.addActionListener(e -> {
            PressureTestConfigDialog pressureTestConfigDialog = new PressureTestConfigDialog(this.psiMethod.getProject());
            pressureTestConfigDialog.show();
            if (pressureTestConfigDialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
                //获取多线程配置
                PressureTestConfig pressureTestConfig = pressureTestConfigDialog.getPressureTestForm().getThreadPoolConfig();
                this.doExecute(pressureTestConfig);
            }
        });
    }

    /**
     * 下载按钮
     */
    private void initDownloadBtn() {
        SwingUtils.clearActionListener(this.downloadBtn);
        this.downloadBtn.addActionListener(e -> {
            try {
                Map<String, String> headerMap = this.getHeaderMap();
                if (headerMap.containsKey(EasyTestingHeaders.IS_HTTP_REQUEST)) {
                    //http请求才下载
                    this.isDownload = true;
                    //选择下载地址
                    FileSaverDescriptor descriptor = new FileSaverDescriptor("Save File", "Select a directory to save the file");
                    FileSaverDialog dialog = FileChooserFactory.getInstance().createSaveFileDialog(descriptor, psiMethod.getProject());
                    String fileName = "defaultFileName";
                    VirtualFile baseDir = null;
                    if (Objects.nonNull(lastRsp)) {
                        File file = FileUtil.file(lastRsp.getData().toString());
                        if (file.exists()) {
                            VirtualFile lastFile = VirtualFileManager.getInstance().findFileByNioPath(file.toPath());
                            if (Objects.nonNull(lastFile)) {
                                baseDir = lastFile;
                                fileName = lastFile.getName();
                            }
                        }
                    }
                    VirtualFileWrapper virtualFileWrapper = dialog.save(baseDir, fileName);
                    if (Objects.isNull(virtualFileWrapper)) {
                        this.isDownload = false;
                        return;
                    }
                    File chooseFile = virtualFileWrapper.getFile();
                    String absolutePath = chooseFile.getAbsolutePath();
                    this.downloadPath = absolutePath;

                }
                this.executeBtn.doClick();
            } finally {
                this.resetStatus();
            }
        });
    }

    /**
     * 重置状态
     */
    private void resetStatus() {
        this.isDownload = false;
        this.isTxRollbackCover = false;
    }

    /**
     * 执行按钮
     */
    private void initExecuteBtn() {
        //点击执行
        SwingUtils.clearActionListener(this.executeBtn);
        this.executeBtn.addActionListener(e -> {
            doExecute(null);
        });
    }

    /**
     * 执行
     */
    private void doExecute(@Nullable PressureTestConfig pressureTestConfig) {
        if (CollectionUtil.isNotEmpty(paramMap)) {
            executeParamMap = new LinkedHashMap<>();
            historyParamMap.clear();
            paramMap.forEach((k, textComponent) -> {
                String text = "";
                if (Objects.nonNull(textComponent)) {
                    text = textComponent.getText();
                }
                PsiParameter psiParameter = psiParamMap.get(k);
                String typeStr = textComponent.getTypeStr(psiParameter);
                executeParamMap.put(k, new MethodParameter(typeStr, text));
            });
        }
        //获取端口
        List<RegistryData> registryDataList = RegistryCenter.getRegisterServer(this.psiMethod.getContainingFile());
        if (CollectionUtil.isEmpty(registryDataList)) {
            this.cancelWorker.run();
            return;
        }

        AtomicReference<RegistryData> registryDataHolder = new AtomicReference<>();
        if (registryDataList.size() == 1) {
            registryDataHolder.set(registryDataList.get(0));
        }

        RegistryData registryData = registryDataHolder.get();
        if (Objects.isNull(registryData)) {
            //弹窗
            this.chooseRegistryData(registryDataList, registryDataHolder, pressureTestConfig);
        } else {
            doExecute(registryData, pressureTestConfig);
        }
    }

    @Override
    public JComponent getMainPanel() {
        return this.mainPanel;
    }

    private Map<String, String> getHeaderMap() {
        HttpHeaderTableModel httpHeaderTableModel = (HttpHeaderTableModel) this.headerTable.getModel();
        List<HeaderMapping> rowList = httpHeaderTableModel.getRowList();
        Map<String, String> headerMap = rowList.stream()
                .filter(headerMapping -> StrUtil.isNotBlank(headerMapping.getKey()))
                .peek(headerMapping -> {
                    if (Objects.isNull(headerMapping.getValue())) {
                        headerMapping.setValue("");
                    }
                })
                .collect(Collectors.toMap(HeaderMapping::getKey, HeaderMapping::getValue, (v1, v2) -> v1, LinkedHashMap::new));
        LinkedHashMap<String, Boolean> statusMap = rowList.stream()
                .filter(headerMapping -> StrUtil.isNotBlank(headerMapping.getKey()))
                .peek(headerMapping -> {
                    if (Objects.isNull(headerMapping.getEnable())) {
                        headerMapping.setEnable(false);
                    }
                })
                .collect(Collectors.toMap(HeaderMapping::getKey, HeaderMapping::getEnable, (v1, v2) -> v1, LinkedHashMap::new));
        IEasyTestingCache easyTestingCache = IEasyTestingCache.getInstance(ProjectUtils.getCurrProject());
        easyTestingCache.cacheHeaderMap(headerMap);
        easyTestingCache.cacheHeaderStatusMap(statusMap);
        Map<String, String> newHeaderMap = easyTestingCache.getHeaderMap();
        Map<String, Boolean> headerStatusMap = easyTestingCache.getHeaderStatusMap();
        headerStatusMap.forEach((k, enable) -> {
            if (!enable) {
                newHeaderMap.remove(k);
            }
        });
        return newHeaderMap;
    }

    /**
     * 获取数据
     *
     * @param registryDataList
     * @param pressureTestConfig
     * @return
     */
    private void chooseRegistryData(
            List<RegistryData> registryDataList,
            AtomicReference<RegistryData> registryDataHolder,
            @Nullable PressureTestConfig pressureTestConfig
    ) {
        ListPopup listPopup = JBPopupFactory.getInstance().createListPopup(
                new BaseListPopupStep<>(
                        "Choose The Running App",
                        registryDataList
                ) {


                    @Override
                    public @NotNull String getTextFor(RegistryData value) {
                        return value.getAppName();
                    }

                    @Override
                    public @Nullable PopupStep<?> onChosen(RegistryData selectedValue, boolean finalChoice) {
                        doExecute(selectedValue, pressureTestConfig);
                        return PopupStep.FINAL_CHOICE;
                    }


                }
        );
        listPopup.showCenteredInCurrentWindow(psiMethod.getProject());
    }

    /**
     * 执行
     *
     * @param registryData
     * @param pressureTestConfig
     */
    private void doExecute(RegistryData registryData, @Nullable PressureTestConfig pressureTestConfig) {
        cancelWorker.run();

        MethodInvokeData methodInvokeData = this.buildMethodInvokeData();
        long executeTime = System.currentTimeMillis();

        PersistenceEasyTestingCache easyTestingCache = (PersistenceEasyTestingCache) IEasyTestingCache
                .getInstance(psiMethod.getProject());

        String executePath = easyTestingCache.getParamCachePath(easyTestingCache.getMethodKey(psiMethod)) + "/" + executeTime;

        methodInvokeData.setExecutePath(executePath);
        LinkedHashMap<String, Object> map = Maps.newLinkedHashMap();
        Map<String, MethodParameter> parameterMap = methodInvokeData.getParameterMap();
        parameterMap.forEach((k, v) -> {
            map.put(k, v.getValue());
        });


        ExecuteResult executeResult = new ExecuteResult(map, null, executeTime, false, executePath);
        easyTestingCache.cacheParamMap(psiMethod, executeResult);
        easyTestingCache.cacheExecuteScript(psiMethod, executeResult);

        EasyTestingThreadPool.getBizThreadPool().execute(() -> {
            if (Objects.isNull(pressureTestConfig)) {
                ApplicationManager.getApplication().runReadAction(() -> {
                    try {
                        RpcClient.sendExecuteBlock(
                                executeResult,
                                methodInvokeData,
                                registryData.getPort(),
                                executeResultHandler
                        );
                    } catch (Exception e) {
                        //ignore
                    }
                });
            }else {
                PressureTestTaskStarter pressureTestTaskStarter = new PressureTestTaskStarter(pressureTestConfig, registryData.getPort(), methodInvokeData);
                pressureTestTaskStarter.run();
            }
        });
    }

    /**
     * 构建方法元数据
     * @return
     */
    private MethodInvokeData buildMethodInvokeData() {
        MethodInvokeData methodInvokeData = new MethodInvokeData();
        if (Objects.nonNull(enumInstanceComboBox)) {
            String enumTarget = enumInstanceComboBox.getSelectedItem().toString();
            if (StrUtil.isNotBlank(enumTarget)) {
                methodInvokeData.setEnumInstanceMethod(true);
                methodInvokeData.setEnumTarget(enumTarget);
            }
        }

        methodInvokeData.setClassQualifiedName(PsiTreeUtil.getParentOfType(psiMethod, PsiClass.class).getQualifiedName());
        methodInvokeData.setMethodName(psiMethod.getName());
        methodInvokeData.setParameterMap(executeParamMap);
        methodInvokeData.setHeaderMap(getHeaderMap());
        methodInvokeData.setDownload(isDownload);
        methodInvokeData.setDownloadPath(downloadPath);
        methodInvokeData.setTxRollbackCover(this.isTxRollbackCover);
        return methodInvokeData;
    }

    private void createUIComponents() {
        // TODO: place custom component creation code here
        this.headerScrollPane = new JBScrollPane();
        this.methodParamScroll = new JBScrollPane();
    }

    @Override
    public @NotNull JComponent getToolPanel() {
        return this.toolPanel;
    }
}
